package hit.core;

import hit.persistence.SerializationHandler;

import java.io.File;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import hit.reports.Visitor;
import hit.util.autoProductID.ProductIdentifier;

/**
 * The HomeInventoryTracker is the root object in the tree of the HIT system.
 * The class is a Singleton, as only one should ever be created in any given system.
 * */
@SuppressWarnings("serial")
public class HomeInventoryTracker extends HITBase {
	private List<StorageUnit> storageUnits;
	private static HomeInventoryTracker singletonHIT = null;
	private ProductIndex productIndex;
	private ItemIndex itemIndex;
	
	private transient TreeFacade treeFacade;
	private transient ProductFacade productFacade;
	private transient ItemFacade itemFacade;
	
	private Date removedReportLastRun;
	
	private int nextItemID;
	//the constructor is private to keep the HomeInventoryTracker from being
	//instantiated
	private HomeInventoryTracker() {
		storageUnits = new ArrayList<StorageUnit>();
		productIndex = new ProductIndex();
		itemIndex = new ItemIndex();
		ResetFacades();
		nextItemID = 0;
		removedReportLastRun = null;
	}
	
	/**
	 * Returns the single instance of the HomeInventoryTracker.
	 * @return the HomeInventoryTracker
	 * */
	public static HomeInventoryTracker Instance() {
		if (singletonHIT == null)
			singletonHIT = new HomeInventoryTracker();
		return singletonHIT;
	}
	
	/**
	 * Testing code!!! Creates a new HIT and returns it.
	 * @return
	 */
	public static HomeInventoryTracker GetEmptyInstance() {
		singletonHIT = new HomeInventoryTracker();
		return singletonHIT;
	}
	
	/**
	 * Determines if a  {@link StorageUnit} can be added to the tree
	 * @param storageUnit the StorageUnit to check
	 * @return whether the StorageUnit can be added
	 * */
	public boolean CanAddStorageUnit(StorageUnit unit) {
		//return false if the unit is null or invalid
		if (unit == null || !unit.IsValid()){
			return false;
		}
		
		//check through all the names of storage units for duplicates
		for (StorageUnit storageUnit : storageUnits) {
			if (unit.GetName().equals(storageUnit.GetName()))
				return false;
		}
		
		return true;
	}
	
	/**
	 * Add a {@link StorageUnit} to the HomeInventoryTracker.
	 * @param unit the {@link StorageUnit} to add. Throws {@link IllegalArgumentException}
	 * if the StorageUnit is invalid.
	 * @throws IllegalArgumentException
	 * */
	public void AddStorageUnit(StorageUnit unit) throws IllegalArgumentException {
		if (!CanAddStorageUnit(unit)){
			throw new IllegalArgumentException();
		}
		storageUnits.add(unit);
	}
	
	/**
	 * Checks to see if the {@link StorageUnit} can be removed.
	 * @param unit The storage unit to check
	 * @return True if can be removed.
	 */
	public Boolean CanRemoveStorageUnit(StorageUnit unit){
		if (!storageUnits.contains(unit)){
			return false;
		}
		//The idea will be to pass unit to the ItemIndex, and see if the list returned is empty
		
		return CanRemoveProductContainer(unit);
	}
	
	/**
	 * Determines whether a {@link ProductContainer} can be removed from the HIT.
	 * A ProductContainer can be removed if if does not contain {@link Item}s and its children
	 * can also be removed.
	 * @param container the ProductContainer to check
	 * */
	public boolean CanRemoveProductContainer(ProductContainer container) {		
		Set<Item> items = itemIndex.GetItemsByProductContainer(container);
		boolean result = (items == null || items.size() == 0);
		
		Iterator<ProductGroup> iter = container.GetProductGroupIterator();
		while (result && iter.hasNext())
		{
			result = result && CanRemoveProductContainer(iter.next());
		}
		
		return result;
	}
	
	/**
	 * Removes a {@link StorageUnit} from the HomeInventoryTracker.
	 * @param unit the {@link StorageUnit} to remove. Throws {@link IllegalArgumentException}
	 * if it can not be removed.
	 * @throws IllegalArgumentException
	 * */
	public void RemoveStorageUnit(StorageUnit unit) throws IllegalArgumentException{
		if (!CanRemoveStorageUnit(unit)){
			throw new IllegalArgumentException();
		}
		storageUnits.remove(unit);
	}
	
	/**
	 * Get a {@link StorageUnit} by name.
	 * @param name the name of the {@link StorageUnit} to get.
	 * @return the {@link StorageUnit} unit
	 * @throws IllegalArgumentException
	 * */
	public StorageUnit GetStorageUnit(String name) throws IllegalArgumentException{
		//int result = storageUnits.indexOf(new StorageUnit(name));
		
		if (name == null)
			throw new IllegalArgumentException();
		
		for (int i = 0; i < storageUnits.size(); i++)
		{
			if (storageUnits.get(i).GetName().equals(name))
				return storageUnits.get(i);
		}

		return null;
	}
	
	/**
	 * Increments to the next available local {@link Barcode}
	 * @return A valid {@link Item} {@link Barcode}
	 */
	public Barcode GetNextItemBarcode(){
		nextItemID++;
		return Barcode.GetLocalBarcode(nextItemID - 1);
	}
	
	/**
	 * @return What the next internal item id will be
	 */
	public int GetNextItemID(){
		return nextItemID;
	}
	
	public void SetNextItemID(int id) {
		nextItemID = id;
	}
	
	/**
	 * Gets the {@link Iterator} for the list of {@link StorageUnit}s.
	 * @return The Iterator.
	 */
	public Iterator<StorageUnit> GetStorageUnitIterator() {
		
		return storageUnits.iterator();
	}
	
	
	//------------------------------------------------------------------------------
	//Accessors
	//------------------------------------------------------------------------------
	/**
	 * @return the ProductIndex
	 * */
	public ProductIndex GetProductIndex(){
		return productIndex;
	}
	
	/**
	 * @return the ItemIndex
	 * */
	public ItemIndex GetItemIndex(){
		return itemIndex;
	}
	
	/**
	 * @return the treeFacade
	 */
	public TreeFacade GetTreeFacade() {
		return treeFacade;
	}
	
	/**
	 * @return the productFacade
	 */
	public ProductFacade GetProductFacade() {
		return productFacade;
	}

	/**
	 * @return the itemFacade
	 */
	public ItemFacade GetItemFacade() {
		return itemFacade;
	}
	
	public void SetRemovedReportLastRun(Date date)
	{
		this.removedReportLastRun = date;
	}
	
	public Date GetRemovedReportLastRun()
	{
		return this.removedReportLastRun;
	}
	
	/**
	 * Reset all the Facades
	 * */
	public void ResetFacades() {
		treeFacade = new TreeFacade(this);
		productFacade = new ProductFacade(this.productIndex);
		itemFacade = new ItemFacade(this.itemIndex);
	}
	
	/**
	 * Reload the previous HIT, set the Singleton, and reset the facades
	 * */
	public static void LoadHit(HomeInventoryTracker toLoad){
		
		if (toLoad == null)
			singletonHIT = new HomeInventoryTracker();
		else	
			singletonHIT = toLoad;
		
		if (singletonHIT != null)
			singletonHIT.ResetFacades();
	}
	
	public void Accept(Visitor visitor){
		visitor.VisitHITCore(Instance());
		for (StorageUnit su : storageUnits){
			su.Accept(visitor);
		}
	}
	
	public void AcceptPost(Visitor visitor){
		visitor.VisitHITCore(Instance());
		for (StorageUnit su : storageUnits){
			su.AcceptPost(visitor);
		}
	}
}
